package com.aforce3.trailertracer.scenes;

import javax.media.j3d.Transform3D;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import com.aforce3.trailertracer.Frame;
import com.aforce3.trailertracer.Ray;
import com.aforce3.trailertracer.optimizations.BoundingBox;
import com.aforce3.trailertracer.optimizations.SceneBoundingBox;
import com.aforce3.trailertracer.utils.Color;
import com.aforce3.trailertracer.utils.ColorModifier;

public class Sphere extends Shape
{
	Point3d center;
	Point3d unTransformedCenter;
	float radius;

	public Sphere(Point3d center, float radius)
	{
		this.center = center;
		this.unTransformedCenter = center;
		this.radius = radius;
	}

	public Sphere(Point3d center, float radius, float index)
	{
		this.center = center;
		this.radius = radius;
		this.indexOfRefraction = index;
	}

	public Point3d getCenter()
	{
		return center;
	}

	public float getRadius()
	{
		return radius;
	}
	
	//TODO: Verify that this actually works properly
	public BoundingBox getBoundingBox()
	{
		Point3d upperLeftBack = new Point3d(center);
		Point3d lowerRightFront = new Point3d(center);

		upperLeftBack.sub(new Vector3d(radius, -radius, radius));
		lowerRightFront.add(new Vector3d(radius, -radius, radius));
		
		return new SceneBoundingBox(upperLeftBack, lowerRightFront, this);
	}

	@Override
	public Color3f getColor(Ray ray, Scene child)
	{
		float distance = getDistance(ray);

		Point3d intersect = new Point3d();
		Vector3d temp = new Vector3d(ray.getDirection());
		temp.scale(distance);
		temp.add(ray.getSource());
		intersect.x = temp.x;
		intersect.y = temp.y;
		intersect.z = temp.z;

		if(distance > 0)
		{

			Color3f reflectedColor = null;
			Color3f refractedColor = null;
			Ray newRefractionRay = calculateRefraction(ray, intersect);
			double refractionDistance = 0;
			// point is in triangle, return color
			if(ray.depth < Frame.depth)
			{
				Ray newReflectionRay = calculateReflection(ray, intersect);
				newReflectionRay.setSource(intersect);
				reflectedColor = getRayColor(newReflectionRay);

				// get distance within spheres
				refractionDistance = intersect.distance(newRefractionRay.getSource());

				// System.out.println(refractionDistance);

				refractedColor = getRayColor(newRefractionRay);
				// refracted color is the refracted color plus the ambient color of the refract interesect
				// screw the other parts for now
				// refractedColor.add(ColorModifier.calculateLight(getRoot(), this, incomingRay, newRefractionRay.getSource()));
			}

			// this is ambient color
			Color returnColor = new Color(ColorModifier.calculateLight(getRoot(), this, ray, intersect));

			if(reflectedColor != null)
			{
				reflectedColor.scale(material.getReflectivity());
				returnColor.add(reflectedColor);
			}

			returnColor.scale(1 - material.getTransparency());

			float transparency = material.getTransparency();

			Color3f refractedPointColor = new Color3f(ColorModifier.calculateRefractedPoint(getRoot(), this, newRefractionRay));

			if(refractedColor != null)
			{
				// refractedColor.add(material.getAmbientColor());
				// float transparency = material.getTransparency() - (float) refractionDistance / 50;

				// refractedColor.scale(material.getTransparency());
				refractedColor.clamp(0, 1);
				refractedPointColor.add(refractedColor);

				// System.out.println(refractedPointColor);
				// refractedColor.add(refractedPointColor);
				// refractedColor.scale(transparency);

				// refractedColor.clamp(0, 1);
				// System.out.println(refractedColor);

			}
			refractedPointColor.scale(material.getTransparency());
			returnColor.add(refractedPointColor);

			returnColor.clamp(0, 1);

			return returnColor;
		}

		return null;
	}

	@Override
	public float getDistance(Ray ray)
	{
		// center minus (relative to) source
		Vector3d CminusS = new Vector3d(center);
		CminusS.sub(ray.getSource());

		float LtimesC = (float) (ray.getDirection().x * CminusS.x + ray.getDirection().y * CminusS.y + ray.getDirection().z * CminusS.z);
		float CSquared = (float) (CminusS.x * CminusS.x + CminusS.y * CminusS.y + CminusS.z * CminusS.z);
		float rSquared = radius * radius;

		float inner = LtimesC * LtimesC - CSquared + rSquared;

		if(inner < 0)
		{
			return -1;
		}
		else
		{
			float positive = (float) (LtimesC + Math.sqrt(inner));
			float negative = (float) (LtimesC - Math.sqrt(inner));
			// make sure it is not too close
			if(negative > 0.01)
			{
				return negative;
			}
			else if(positive > 0)
			{
				// returning point from within sphere :D
				return positive;
			}
			else
			{
				return -1;
			}
		}
	}

	@Override
	public Vector3d getNormal(Ray ray)
	{
		float distance = getDistance(ray);
		Vector3d temp = new Vector3d(ray.getDirection());
		temp.scale(distance);
		temp.add(ray.getSource());
		Vector3d intersect = new Vector3d(temp);
		Vector3d normal = new Vector3d(intersect);
		normal.sub(center);
		normal.normalize();
		return normal;
	}

	@Override
	public Vector3d getNormal(Point3d intersect)
	{
		Vector3d normal = new Vector3d(intersect);
		normal.sub(center);
		normal.normalize();
		return normal;
	}

	public float getRefractionIndex()
	{
		return indexOfRefraction;
	}

	public void setRefractionIndex(float index)
	{
		indexOfRefraction = index;
	}

	@Override
	protected void applyTransform(Transform3D t)
	{
		t.transform(center);
	}

	@Override
	public void applyTransform()
	{
		center = new Point3d(unTransformedCenter);
		transformation.transform(center);
	}
}
